package cn.bigcontainer.alt.meta.dto;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonIgnore;

import cn.bigcontainer.alt.common.entity.BigMap;
import cn.bigcontainer.alt.common.util.WoKit;
import cn.bigcontainer.alt.meta.dao.MDao;
import cn.bigcontainer.alt.meta.exception.MException;
import cn.bigcontainer.alt.meta.extension.MListDataFilter;
import cn.bigcontainer.alt.meta.po.MObject;
import cn.bigcontainer.alt.meta.po.MProperty;
import cn.bigcontainer.alt.meta.po.MRelation;
import cn.bigcontainer.alt.meta.util.MUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MObjectDto extends MObject {

    /**
     * 名称属性对应的key
     */
    public final static String PROP_NAME = "name";

    /**
     * 逻辑删除属性对应的key
     */
    public final static String PROP_LOGIC = "logic";

    /**
     * 自关联对象编号属性的key，在SPROPS中的值为：{"woNoProp":"W_Z"}
     */
    public final static String PROP_NO = "no";

    /**
     * 自关联对象编号属性的key，在SPROPS中的值为：{"woUniqueProps":["W_X,W_Y", "W_Z"]}
     */
    public final static String PROP_UNIQUE = "uniques";

    /**
     * 父关系的key
     */
    public final static String RELATION_PARENT = "parentRelation";

    /**
     * 自关联对象编号属性的长度
     */
    public final static String PARAM_LEN_NO = "noPropLength";

    /**
     * HttpServletRequest中的数据
     */
    public final static String PARAM_REQ = "requestData";

    public final static String PREFIX_LIST_FILTER = "listFilter.";

    /**
     * 控件路径
     */
    public final static String PARAM_URL = "url";

    /**
     * 控件根路径
     */
    public final static String PARAM_URL_ROOT = "urlRoot";

    /**
     * data中保存的关系最大编号，当关系创建时加1
     */
    public final static String PARAM_RELATION_NUM = "relationNumber";

    /**
     * data中保存的设置对象是否处于元数据编辑状态
     */
    public final static String PARAM_META = "meta";

    /**
     * data中保存在内存中的设置对象处于元数据编辑状态时，是否显示
     */
    public final static String PARAM_META_SHOW = "metaShow";

    public MObjectDto() {
    }

    public MObjectDto(MObject po) {
       super(po);
    }

    public void setRequest (BigMap<String, Object> request) {
        data.put(PARAM_REQ, request);
    }

    @JsonIgnore
    public BigMap<String, Object> getRequest () {
        Object o = data.get(PARAM_REQ);
        if (o == null) {
        	BigMap<String, Object> bigMap = new BigMap<>();
            data.bigPut(PARAM_REQ, bigMap);
            return bigMap;
        }
        return (BigMap<String, Object>)o;
    }

    public static MProperty getNameProperty (MObject object) {
        List<String> namePropertyName = new ArrayList<>();
        Object o = object.getColumns().get(PROP_NAME);
        if (!WoKit.isEmpty(o)) {
            namePropertyName.add(o.toString());
        }
        if (namePropertyName.size() == 0) {
        	namePropertyName.addAll(Arrays.asList("mmc", "mname", "name", "mbt", "mxm"));
        }
        for (MProperty p : object.getProperties()) {
            if (namePropertyName.contains(p.getName())) {
                return p;
            }
        }
        // 第一个不是主键的文本字段作为nameProperty
        List<MProperty> keyProperties = MObjectDto.getKeyProperties(object);
        for (MProperty p : object.getProperties()) {
            if (keyProperties.contains(p)) {
                continue;
            }
            if (Arrays.asList(MProperty.TYPE_VARCHAR, MProperty.TYPE_TEXT).contains(p.getType())) {
                return p;
            }
        }
        return null;
    }

    public MProperty getNameProperty () {
        return getNameProperty(this);
    }
    
    public static List<MProperty> getKeyProperties (MObject object) {
        List<MProperty> properties = new ArrayList<>();
        object.getProperties().forEach(p -> {
            if (p.getKey() == 1) {
                properties.add(p);
            }
        });
        return properties;
    }

    public static String getKeyIds (MObject object) {
        List<MProperty> properties = getKeyProperties(object);
        return MUtil.getIds(properties);
    }

    public Object[] getNameValue(String idData) {
        List<Object[]> valsList = new ArrayList<>();
        String[] strings = idData.split(",");
        int keySize = getKeyProperties().size();
        for (int i = 0; i < strings.length / keySize; i ++) {
            Object [] objs = new Object[keySize];
            for (int j = 0; j < objs.length; j ++) {
                objs[j] = strings[j + i * objs.length];
            }
            valsList.add(objs);
        }
        return getNameValue(valsList, this);
    }

    public Object[] getNameValue(List<Object[]> valsList) {
        return getNameValue(valsList, this);
    }

    /**
     *
     * @param valsList
     * @param object
     * @return
     */
    public static Object[] getNameValue(List<Object[]> valsList, MObject object) {
        if (WoKit.isArrayOrListEmpty(valsList)) {
            return null;
        }
        List<MProperty> keys = getKeyProperties(object);
        Object[] nameVals = new Object[valsList.size()];
        String nameCol = getNameProperty(object).getName();
        String sql = "select " + nameCol + " from " + object.getId() + " where (";
        for (int i = 0; i < keys.size(); i++) {
            MProperty key = keys.get(i);
            if (i > 0) {
                sql += ",";
            }
            sql += key.getName();
        }
        sql += ") in (";
        for (int n = 0; n < nameVals.length; n++) {
            Object[] vals = valsList.get(n);
            if (vals.length != keys.size()) {
                throw new MException(MException.PK_NUM, object.getId(), Arrays.toString(vals));
            }
            if (n > 0) {
                sql += ",";
            }
            sql += "(";
            for (int i = 0; i < vals.length; i++) {
                MProperty key = keys.get(i);
                if (i > 0) {
                    sql += ",";
                }
                sql += key.getSqlValue(vals[i]);
            }
            sql += ")";
        }
        sql += ")";
        List<Map<String, Object>> list = WoKit.getApplicationContext().getBean(MDao.class).queryList(sql);
        if (WoKit.isArrayOrListEmpty(list)) {
            throw new MException(MException.OBJ_NAME, object.getId());
        }
        Object[] vals = new Object[list.size()];
        for (int i = 0; i < vals.length; i++) {
            Map<String, Object> l = list.get(i);
            vals[i] = l.get(nameCol);
        }
        return vals;
    }

    private static String getPropsEqualNullOrEmptyCondition(MObject object, List<MProperty> props, boolean hasTableAlias) {
        StringBuffer sv = new StringBuffer();
        for (int i = 0; i < props.size(); i++) {
            MProperty p = props.get(i);
            if (i > 0) {
                sv.append(" and ");
            }
            sv.append("(");
            if (hasTableAlias) {
                sv.append(object.getNo() + ".");
            }
            sv.append(p.getName());
            sv.append(" is null ");
            if (p.getType().equals(MProperty.TYPE_VARCHAR)) {
                sv.append(" or ");
                if (hasTableAlias) {
                    sv.append(object.getNo() + ".");
                }
                sv.append(p.getName());
                sv.append("=''");
            }
            sv.append(")");
        }
        return sv.toString();
    }

    private static String getPropsEqualCondition(MObject object, List<MProperty> props, boolean hasTableAlias, String val) {
        StringBuffer sf = new StringBuffer();
        StringBuffer sv = new StringBuffer();
        String[] vals = val.split(",");
        for (int i = 0; i < props.size(); i++) {
            MProperty p = props.get(i);
            if (i > 0) {
                sf.append(",");
                sv.append(",");
            }
            if (hasTableAlias) {
                sf.append(object.getNo() + ".");
            }
            sf.append(p.getName());
            sv.append(p.getSqlValue(vals[i]));
        }
        if (props.size() > 1) {
            sf.insert(0, "(");
            sf.append(")");
            sv.insert(0, "(");
            sv.append(")");
        }
        sf.append("=" + sv);
        return sf.toString();
    }

    /**
     * @param props
     * @param hasTableAlias
     *            true:(APPS.W_ID,APPS.W_NAME)=('',''),false:(W_ID,W_NAME)=('','
     *            ')
     * @param val
     *            多个属性值逗号隔开
     * @return
     */
    public static String getPropsEqualConditions(MObject object, List<MProperty> props, boolean hasTableAlias, String val) {
        String[] keyValsArray = val.split(",");
        int keys = props.size();
        String[] newArray = new String[keyValsArray.length / keys];
        String cdt = null;
        for (int i = 0; i < newArray.length; i++) {
            for (int j = 0; j < keys; j++) {
                if (newArray[i] == null) {
                    newArray[i] = keyValsArray[j + i * keys];
                } else {
                    newArray[i] += "," + keyValsArray[j + i * keys];
                }
            }
            String c = getPropsEqualCondition(object, props, hasTableAlias, newArray[i]);
            if (cdt == null) {
                cdt = c;
            } else {
                cdt += " or " + c;
            }
        }
        return cdt;
    }

    /**
     * @param hasTableAlias
     * @param val
     * @return
     */
    public static String getKeysEqualConditions(MObject object, boolean hasTableAlias, String val) {
        return getPropsEqualConditions(object, MObjectDto.getKeyProperties(object), hasTableAlias, val);
    }

    public String getKeysEqualConditions(boolean hasTableAlias, String val) {
        return getKeysEqualConditions(this, hasTableAlias, val);
    }

    /**
     * @param hasTableAlias
     * @param val
     * @return
     */
    public static String getKeysEqualCondition(MObject object, boolean hasTableAlias, String val) {
        return getPropsEqualCondition(object, MObjectDto.getKeyProperties(object), hasTableAlias, val);
    }

    public String getKeysEqualCondition(boolean hasTableAlias, String val) {
        return getPropsEqualCondition(this, this.getKeyProperties(), hasTableAlias, val);
    }

    public Boolean hasProperty (String propKey) {
        return columns.containsKey(propKey);
    }

    public Boolean hasNoProperty () {
        return hasProperty(PROP_NO);
    }

    public static String getNoPropName(MObject object) {
        Object o = object.getColumns().get(PROP_NO);
        return o == null ? null : (String)o;
    }

    @JsonIgnore
    public String getNoPropName() {
        return getNoPropName(this);
    }

    @JsonIgnore
    public MProperty getNoProperty () {
        String noPropName = MObjectDto.getNoPropName(this);
        if (noPropName == null) {
            return null;
        }
        Optional<MProperty> first = this.getProperties().stream().filter(e -> e.getName().equals(noPropName)).findFirst();
        if (first.isPresent()) {
            return first.get();
        }
        return null;
    }

    /**
     * @param keyValues
     * @return
     */
    private static String getNoValue(MObject object, String keyValues) {
        String noSql = "select " + getNoPropName(object) + " from " + object.getId() + " where "
                + getKeysEqualConditions(object,false, keyValues);
        Map<String, Object> noData = WoKit.getApplicationContext().getBean(MDao.class).queryOne(noSql);
        if (noData == null) {
            return null;
        }
        Object val = noData.get(getNoPropName(object));
        if (WoKit.isEmpty(val)) {
            return null;
        }
        return val.toString();
    }

    public static MRelation getRelation(MObject object, Object relationId) {
        if (WoKit.isEmpty(relationId)) {
            return null;
        }
        for (MRelation r : object.getRelations()) {
            if (r.getId().equals(relationId)) {
                return r;
            }
        }
        throw new MException(MException.OBJ_R, object.getId(), relationId);
    }

    /**
     * @param relationId
     * @return
     */
    public MRelation getRelation(Object relationId) {
    	return getRelation(this, relationId);
    }
    
    /**
     * @return
     */
    public static MRelation getParentRelation(MObject object) {
        Object id = object.getData().get(RELATION_PARENT);
        MRelation relation = null;
        if (!WoKit.isEmpty(id)) {
            relation = getRelation(object, id);
        }
        if (relation == null) {
            for (MRelation r : object.getRelations()) {
                if (r.getRelatedObjectId().equals(object.getId())) {
                    relation = r;
                }
            }
        }
        return relation;
    }

    public MRelation getParentRelation() {
        return getParentRelation(this);
    }

    public static MProperty getProperty (MObject object, String id) {
        for (MProperty p : object.getProperties()) {
            if (p.getId().equals(id)) {
                return p;
            }
        }
        return null;
    }

    public static MProperty getPropertyByName (MObject object, String name) {
        for (MProperty p : object.getProperties()) {
            if (p.getName().equals(name)) {
                return p;
            }
        }
        return null;
    }

    public static List<MProperty> getProperties (MObject object, String idsStr) {
        String[] ids = idsStr.split(",");
        List<MProperty> properties = new ArrayList<>();
        for (String id : ids) {
            MProperty property = getProperty(object, id);
            if (property == null) {
                continue;
            }
            properties.add(property);
        }
        return properties;
    }

    public static List<MProperty> getParentRelationProperties (MObject object) {
        MRelation parentRelation = getParentRelation(object);
        return getProperties(object, parentRelation.getPropertyIds());
    }

    /**
     * @return
     */
    public static int getNoPropLength(MObject object) {
        return WoKit.getInt(object.getData().get(PARAM_LEN_NO), 4);
    }

    /**
     * 创建keyValues对应的上级的下级编号，使其为上级的最大下级编号+1
     *
     * @param keyValues
     * @return
     */
    public static String createNextChildNoValue(MObject object, String keyValues) {
        String noSql = "select max(" + getNoPropName(object) + ") as noValue from " + object.getId() + " where ";
        List<MProperty> parentRelationProperties = getParentRelationProperties(object);
        if (WoKit.isEmpty(keyValues)) {
            noSql += getPropsEqualNullOrEmptyCondition(object, parentRelationProperties, false);
        } else {
            noSql += getPropsEqualCondition(object, parentRelationProperties, false, keyValues);
        }
        Map<String, Object> noData = WoKit.getApplicationContext().getBean(MDao.class).queryOne(noSql);
        Object val = null;
        if (noData != null) {
            val = noData.get("noValue");
        }
        int len = getNoPropLength(object);
        if (WoKit.isEmpty(val)) {
            String no = "";
            for (int i = 0; i < len; i++) {
                no += "0";
            }
            String pId = getNoValue(object, keyValues);
            if (pId == null) {
                throw new MException(MException.NO_PARENT_VAL, object.getId());
            }
            return pId + no;
        }
        return WoKit.createNextNo(val.toString(), len);
    }

    public String createNextChildNoValue(String keyValues) {
        return createNextChildNoValue(this, keyValues);
    }

    /**
     * @param keyValues
     * @return
     */
    public static String getDeleteSQL(String keyValues, MObject object) {
        String noName = getNoPropName(object);
        String sql = "delete from " + object.getId() + " where ";
        String where = getPropsEqualConditions(object, MObjectDto.getKeyProperties(object), false, keyValues);
        if (WoKit.isEmpty(noName)) {
            sql += where;
        } else {
            String sql2 = "select " + noName + " from " + object.getId() + " where " + where;
            List<Map<String, Object>> nos = WoKit.getApplicationContext().getBean(MDao.class).queryList(sql2);
            for (int i = 0; i < nos.size(); i++) {
                Map<String, Object> no = nos.get(i);
                if (i > 0) {
                    sql += " or ";
                }
                sql += noName + " like '" + no.get(noName) + "%'";
            }
        }
        return sql;
    }

    public String getDeleteSQL(String keyValues) {
        return getDeleteSQL(keyValues, this);
    }

    private static List<MProperty> getRelationRelationObjectProperties (MObject object, MRelation relation) {
        return getProperties(object, relation.getRelationIds());
    }

    /**
     * @param keyValues
     * @return
     */
    public static List<String> getDeleteM2MSQL(MObject object, String keyValues) {
        List<MRelation> rs = object.getRelations();
        List<String> sqls = new ArrayList<String>();
        for (MRelation r : rs) {
            if (r.isM2M()) {
                String sql = "delete from " + r.getRelationObjectId() + " where "
                        + getPropsEqualConditions(object, getProperties(r.getRelationObject(), r.getRelationIds()), false, keyValues);
                sqls.add(sql);
            }
        }
        return sqls;
    }

    public List<String> getDeleteM2MSQL(String keyValues) {
        return getDeleteM2MSQL(this, keyValues);
    }

    /**
     * @param keyValues
     * @return
     */
    public static List<String> getUpdate12MSQL(MObject object, String keyValues) {
        List<MRelation> rs = object.getRelations();
        List<String> sqls = new ArrayList<String>();
        for (MRelation r : rs) {
            if (r.is12M()) {
                String sql = "update " + r.getRelatedObjectId() + " set ";
                List<MProperty> rProps = getProperties(object, r.getRelatedIds());
                for (int i = 0; i < rProps.size(); i++) {
                    if (i > 0) {
                        sql += ",";
                    }
                    MProperty rP = rProps.get(i);
                    sql += rP.getName() + " = null";
                }
                sql += " where " + getPropsEqualConditions(object, rProps, false, keyValues);
                sqls.add(sql);
            }
        }
        return sqls;
    }

    /**
     * @param keyValues
     * @return
     */
    public static Map<MRelation, String> getCount12MSQL(MObject object, String keyValues) {
        List<MRelation> rs = object.getRelations();
        Map<MRelation, String> sqls = new HashMap<MRelation, String>();
        for (MRelation r : rs) {
            if (r.is12M()) {
                String sql = "select count(*) from " + r.getRelatedObjectId() + " where "
                        + getPropsEqualConditions(object, getProperties(object, r.getRelatedIds()), false, keyValues);
                sqls.put(r, sql);
            }
        }
        return sqls;
    }

    public Map<MRelation, String> getCount12MSQL(String keyValues) {
        return getCount12MSQL(this, keyValues);
    }

    /**
     * @param objId
     * @return
     */
    public static String getDropSQL(String objId) {
        return "drop table if exists " + objId;
    }

    /**
     * @return
     */
    public static String getDropSQL(MObject object) {
        return getDropSQL(object.getId());
    }

    /**
     * @return
     */
    public static boolean isLogicDelete(MObject object) {
        return object.getColumns().get(PROP_LOGIC) != null;
    }

    /**
     * @return
     */
    public static String getCreateSQL(MObject object) {
        List<MProperty> props = object.getProperties();
        String sql = "";
        String ids = "";
        for (MProperty prop : props) {
            if (!sql.equals("")) {
                sql += ",\n";
            }
            sql += " " + prop.getSqlFieldClause(object);
            if (prop.getKey() == 1) {
                if (ids != "") {
                    ids += ",";
                }
                ids += prop.getName();
            }
        }
        if (ids != "") {
            sql += ",primary key (" + ids + ")";
        }
        sql = "create table " + object.getId() + " (\n" + sql + "\n) comment '" + object.getName() + "'";
        return sql;
    }

    /**
     * @param keyValues
     * @return
     */
    public static String getObjectSQL(MObject object, String keyValues) {
        return "select * from " + object.getId() + " where " + getKeysEqualConditions(object,false, keyValues);
    }

    public String getObjectSQL(String keyValues) {
        return getObjectSQL(this, keyValues);
    }

    public MProperty getPropertyIncludeRelatedObjects(String propId) {
        if (WoKit.isEmpty(propId)) {
            return null;
        }
        // 从本对象获取属性
        MProperty prop = MObjectDto.getProperty(this, propId);
        if (prop != null) {
            return prop;
        }
        // 从关系对象获取属性
        for (MRelation r : getRelations()) {
        	List<MObjectDto> relatedObjects = r.getRelatedObjects();
        	for (int i = 0; i < relatedObjects.size(); i++) {
        		prop = relatedObjects.get(i).getProperty(propId);
        		if (prop != null) {
                    return prop;
                }
			}
        }
        throw new MException(MException.PROP_ERR, this.getId(), propId);
    }

    public List<MRelation> getRelations() {
        relations.stream().forEach(r -> r.setObject(this));
        return relations;
    }

    public List<MProperty> getProperties() {
        properties.stream().forEach(p -> p.setObject(this));
        return properties;
    }

    public MProperty getProperty (String id) {
        return MObjectDto.getProperty(this, id);
    }

    public List<MProperty> getKeyProperties () {
        return MObjectDto.getKeyProperties(this);
    }

    /**
     * @param id
     * @param data
     * @param req request数据
     * @return
     */
    public static MListDataFilter getListFilter (String id, BigMap<String, Object> data, BigMap<String, Object> req) {
    	BigMap<String, Object> bigMap = new BigMap<String, Object>().bigPutAll(data).bigPutAll(req);
    	String beanId = bigMap.getString(PREFIX_LIST_FILTER + id);
        if (beanId == null) {
            log.warn("No data in " + PREFIX_LIST_FILTER);
            return null;
        }
        if (beanId.indexOf(' ') > 0) {
        	beanId = MListDataFilter.DEFAULT;
        }
        return WoKit.getBean(MListDataFilter.class, beanId);
    }

    public MListDataFilter getListDataFilter () {
        return MObjectDto.getListFilter(this.id, data, this.getRequest());
    }


    public MProperty getPropertyByName (String name) {
        return getPropertyByName (this, name);
    }

    /**
     * @return
     */
    public List<MProperty[]> getUniqueProps() {
        // {"woUniqueProps":["W_X,W_Y", "W_Z"]}
        List<String> propNames = columns.getList(PROP_UNIQUE);
        List<MProperty[]> list = new ArrayList<MProperty[]>();
        // 获取编号属性名称
        // String noName = this.getNoPropName();
        // 获取编号属性
        // MProperty noProp = this.getPropByName(noName);
        if (WoKit.isEmpty(propNames)) {
            // 设置编号属性为唯一键
            // if (noProp != null) {
            // list.add(new MProperty[] { noProp });
            // }
            return list;
        }
        // 编号属性是否已经在propNames中被设为唯一键
        // boolean noPropIn = false;
        for (int i = 0; i < propNames.size(); i++) {
            String[] names = propNames.get(i).split(",");
            MProperty[] props = new MProperty[names.length];
            // if (props.length == 1 && noProp != null &&
            // names[0].equalsIgnoreCase(noName)) {
            // noPropIn = true;
            // }
            for (int j = 0; j < props.length; j++) {
                props[j] = this.getPropertyByName(names[j]);
            }
            list.add(props);
        }
        // 设置编号属性为唯一键
        // if (!noPropIn && noProp != null) {
        // list.add(new MProperty[] { noProp });
        // }
        return list;
    }

    @JsonIgnore
    public Boolean getMetaShow () {
        return data.getBoolean(MObjectDto.PARAM_META_SHOW, false);
    }

    public void setMetaShow (Boolean metaShow) {
        data.put(MObjectDto.PARAM_META_SHOW, metaShow);
    }

    @JsonIgnore
    public Boolean getMetaShowProperties () {
        for (MProperty p : properties) {
            if (p.getMetaShow()) {
                return true;
            }
        }
        return false;
    }

    @JsonIgnore
    public Boolean getMetaShowRelations () {
        for (MRelation r : relations) {
            if (r.getMetaShow()) {
                return true;
            }
        }
        return false;
    }

    @JsonIgnore
    public String getIdCamelCase () {
        return WoKit.getCamelCase(this.getId(), true);
    }
    
    @JsonIgnore
    public String getIdCamelCase2 () {
        return WoKit.getCamelCase(this.getId(), false);
    }
    
    /**
     * 获取关系上的最大id索引
     * @param object
     * @return
     */
    public static Integer getRelationMaxIdIndex (MObject object) {
    	int index = 0;
    	for (MRelation relation : object.getRelations()) {
    		int i = relation.getId().lastIndexOf("_");
    		Integer index2 = WoKit.getInt(relation.getId().substring(i + 1), 0);
    		if (index2 > index) {
    			index = index2;
    		}
    	}
    	return index;
    }

    @JsonIgnore
    public Boolean isMeta () {
        return "true".equals(data.getString(PARAM_META));
    }
}
